package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
)

/*
	编写学生结构体 实现学生的增删改查 并将结果保存到文件中
*/
//定义学生结构体
type Students struct {
	Id    int      `json:"id"`
	Name  string   `json:"name"`
	Age   int      `json:"age"`
	Class []string `json:"class"`
}

// NewStudent 构造学生结构体的函数
func NewStudent(id int, name string, age int, class []string) *Students {
	return &Students{
		Id:    id,
		Name:  name,
		Age:   age,
		Class: class,
	}
}

// StuMgr 定义一个学生管理者结构体，用来保存学生数据
type StuMgr struct {
	allSts   []map[string]*Students //用来保存学生数据
	dataFile string
}

// NewStuMgr 构造学生管理器的结构体函数
func NewStuMgr(dataFile string) *StuMgr {

	return &StuMgr{
		allSts:   make([]map[string]*Students, 0),
		dataFile: dataFile,
	}
}

/*
	定义一个判断文件是否存在的函数
*/
func IsExistsFile(filePath string) error {
	_, err := os.Stat(filePath)
	if os.IsNotExist(err) {
		return fmt.Errorf("%s 文件不存在", filePath)
	}
	return nil
}

/*
	定义保存数据文件的函数 采用bufio.NewWriter函数

	如果您需要保留之前的数据并在其基础上添加新的数据，您可以使用以下方法：
		1、在保存数据之前，先读取文件中已有的json数组并反序列化为数据结构；
		2、将新的数据追加到旧数据后面；
		3、关闭文件；
		4、重新以写入模式打开同一个文件，这将清空文件的内容；
		5、将整个数据结构序列化为json数据；
		6、写入json数据并关闭文件。
		这样做可以保留之前保存的数据，并且能够正确地将新的数据追加到文件中。
        [{"f":{"id":1,"name":"f","age":0,"class":["w","exir"]}},{"d":{"id":4,"name":"d","age":0,"class":["q","yw"]}}]
*/
func (s *StuMgr) SaveFile() {
	// 先判断数据文件是否存在，不存在则创建文件并写入数据
	if err := IsExistsFile(s.dataFile); err != nil {
		file, err := os.Create(s.dataFile)
		if err != nil {
			fmt.Printf("%s文件创建失败\n", s.dataFile)

			return
		}
		defer file.Close()
		writer := bufio.NewWriter(file)
		data, err := json.Marshal(s.allSts)
		if err != nil {
			fmt.Println("序列化失败")

			return
		}
		_, err = writer.WriteString(string(data))
		if err != nil {
			fmt.Println("写入序列化后的数据失败")
			return
		}
		writer.Flush()
		return
	}

	// 如果文件已存在，则先通过ioutil.ReadFile函数读取已有的数据，并将新数据追加到旧数据后面
	oldData, err := ioutil.ReadFile(s.dataFile)
	fmt.Println(string(oldData))
	if err != nil {
		fmt.Println("读取文件失败")
		return
	}
	//定义新存储数据的文件，并将读到的旧数据反序列化到这个新文件中
	var datasts []map[string]*Students
	err = json.Unmarshal(oldData, &datasts)
	if err != nil {
		fmt.Println("反序列化失败")

		return
	}
	datasts = append(datasts, s.allSts...)
	// 将整个数据结构序列化为json数据
	newData, err := json.Marshal(datasts)
	fmt.Println(string(newData))
	if err != nil {
		fmt.Println("序列化失败")
		return
	}
	// 以写入模式打开文件,清空文件，并写入json数据
	//os.O_WRONLY|os.O_TRUNC 是一个按位或运算符用于对 os.O_WRONLY 和 os.O_TRUNC 这两个常量进行按位或运算的结果
	//os.O_WRONLY 表示以只写模式打开文件。os.O_TRUNC 表示如果文件存在，将截断其长度为 0。
	//使用按位或运算符 | 将两个参数合并在一起，可以同时指定多个选项
	file, err := os.OpenFile(s.dataFile, os.O_WRONLY|os.O_TRUNC, 0644)
	if err != nil {
		fmt.Println("打开文件失败")
		return
	}
	defer file.Close()
	//创建一个写入器，将数据先写入到内存中 writer.Write([]byte) 按字节写入
	writer := bufio.NewWriter(file)
	_, err = writer.Write(newData)
	if err != nil {
		fmt.Println("写入序列化后的数据失败")
		return
	}
	writer.Flush()
}

/*
	定义读文件函数  bufio.NewReader
*/
func (s *StuMgr) LoadFile() []map[string]*Students {
	// 判断数据文件是否存在
	if err := IsExistsFile(s.dataFile); err != nil {
		fmt.Println("文件不存在")
		return nil
	}
	// 以只读模式打开文件
	file, err := os.Open(s.dataFile)
	if err != nil {
		fmt.Println("打开文件失败")

		return nil
	}
	defer file.Close()

	// 创建一个读取器
	reader := bufio.NewReader(file)

	// 读取文件的每一行数据
	for {
		lineBytes, _, err := reader.ReadLine()
		fmt.Println("lineBytes:", string(lineBytes))
		if err == io.EOF {
			break
		} else if err != nil {
			fmt.Println("读取文件失败")
			return nil
		}
		lineString := string(lineBytes)
		fmt.Println("lineString: ", lineString)
		// 将每行数据反序列化为对应的结构体类型，并将其追加到切片中
		var data []map[string]*Students
		err = json.Unmarshal([]byte(lineString), &data)
		if err != nil {
			fmt.Println("反序列化失败")
			return nil
		}
		fmt.Println("读取成功：", data)
		s.allSts = append(s.allSts, data...)
	}
	return s.allSts
}

// Viewallsts 查看全部学生信息
/*
	因为学生信息都保存在学生管理器的结构体中,想要查看学生信息需要一个接收者,这个接收者s *StuMgr返回的是一个经过make初始化的存储空间
	因为是一个key为string类型、value为*Student的切片类型,因此查看学生信息可以通过 切片类型的for循环拿到对应的value，这个value就是map类型
然后这个循环这个value,打印出values为*Student保存的值
*/
func (s *StuMgr) Viewallsts() {
	fmt.Println("查看全部学生信息")
	s.LoadFile()
	//从s.allSts中把数据拿出来并打印
	for _, value := range s.allSts {
		for _, values := range value {
			fmt.Println(values.Id, values.Name, values.Age, values.Class)

		}
	}
}

// Viewoncests 查看单个学生信息
/*
	因为学生信息都保存在学生管理器的结构体中,想要查看学生信息需要一个接收者,这个接收者s *StuMgr返回的是一个经过make初始化的存储空间
该函数接收一个传递进来的int参数,通过输入的id与值为*Student类型中的id对比,如果相等,则打印出这个id对应的用户信息
	接收者表示的是调用该方法的具体类型变量
*/
func (s *StuMgr) Viewoncests(id int) bool {
	fmt.Println("查看单个学生信息")
	for _, value := range s.allSts {
		//fmt.Println(value)
		for _, values := range value {
			//fmt.Println(values)
			if values.Id == id {
				fmt.Println(values.Id, values.Name, values.Age, values.Class)

				return true
			}
		}
	}
	fmt.Println("查看单个学生信息失败，未查询到指定学生信息")
	return false
}

// AddMsgsts  增加学生信息
/*
	该函数接收一个*Students类型的参数 最后把增加的信息通过接收者s *StuMgr 保存到StuMgr结构体中
先是循环切片,拿个每个索引对应的值，该值是map类型。然后在通过map的ok判断方法，检查存储空间中是否有已经存在的键所对应的值，如果有则直接eturn false
如果存储空间中没有，则使用make初始化一个map类型，将传进来的形参保存到这个map类型中,再将这个map通过切片的append方法追加到存储空间中,实现增加功能
*/
func (s *StuMgr) AddMsgsts(student *Students) bool {
	fmt.Println("增加学生信息")
	for _, value := range s.allSts {
		if _, ok := value[student.Name]; ok {
			return false
		}
	}
	s.allSts = make([]map[string]*Students, 0)
	stsmsg := make(map[string]*Students)
	stsmsg[student.Name] = student
	s.allSts = append(s.allSts, stsmsg)
	s.SaveFile()
	return true
}

// UpdateMsgsts 更新函数
/*
	通过传递的形参id,判断想要更新的信息是否存在,先是循环切片，拿到map 再通过循环map，进行传入的id与保存的id进行对比，如果相等,则调用另一个形参student
进行map类型的增加操作，将旧值修改为新值，然后再调用写文件操作，返回一个true
*/
func (s *StuMgr) UpdateMsgsts(id int, student *Students) bool {
	fmt.Println("更新学生信息")
	for _, values := range s.allSts {
		for _, value := range values {
			if value.Id == id {
				value.Id = student.Id
				value.Name = student.Name
				value.Age = student.Age
				value.Class = student.Class
				s.SaveFile()
				return true
			} else {
				fmt.Println("传参id未与map中的id相匹配")
				return false
			}
		}
	}
	return true
}

// DelMsgsts 删除学生信息
/*
	通过传入的形参id判断对应的信息是否存在，先进行切片循环,拿到对应的map 在进行map循环操作,判断*Student值中是否有与形参id一样的值，如果有，则进行
删除map操作，删除后,再进行判断map的长度是否为0了，如果确实是0，则进行切片的删除操作，将切片中的索引进行删除即可
*/
func (s *StuMgr) DelMsgsts(id int) bool {
	fmt.Println("删除学生信息")
	for index, value := range s.allSts {
		//fmt.Println("del: ", data)
		for key, values := range value {
			if values.Id == id {
				delete(value, key)
				if len(value) == 0 {
					s.allSts = append(s.allSts[:index], s.allSts[index+1:]...)
					s.SaveFile()
				}
				return true
			}
		}
	}
	return false
}
